﻿#include <qlib/geo/circle.h>

namespace qlib {

Circle::Circle() {
  radius = 0;
  tolerance = 0.001;
  layer = 0;
  datatype = 0;
}

Circle::Circle(Vec2d _center, double _radius, double _tolerance, uint32_t _layer, uint32_t _datatype) {
  center = _center;
  radius = _radius;
  tolerance = _tolerance;
  layer = _layer;
  datatype = _datatype;
}

Circle::s_ptr Circle::copy() const {
  return std::make_shared<Circle>(*this);
};

void Circle::init(Vec2d _center, double _radius, double _tolerance, uint32_t _layer, uint32_t _datatype) {
  center = _center;
  radius = _radius;
  tolerance = _tolerance;
  layer = _layer;
  datatype = _datatype;
}

void Circle::scale(double s) {
  radius *= s;
}
void Circle::mirror(Vec2d p0, Vec2d p1) {
  Vec2d v = p1 - p0;
  double tmp = v.length_sq();
  if (tmp == 0)
    return;

  Vec2d r = v * (2 / tmp);
  Vec2d p2 = p0 * 2;

  Vec2d* p = &center;
  *p = v * (*p - p0).inner(r) - *p + p2;
}
void Circle::translate(double dx, double dy) {
  center += Vec2d(dx, dy);
}
void Circle::transform(double magnification, bool x_reflection, Vec2d origin) {
  if (x_reflection) {
    center.y *= -1;
  }
  radius *= magnification;
  center += origin;
}

Polygon::s_ptr Circle::to_polygon() const {
  Polygon::s_ptr result = Polygon::create();

  result->layer = layer;
  result->datatype = datatype;
  result->properties = properties;
  result->repetition = repetition;

  double radius_x = radius, radius_y = radius, inner_radius_x = 0, inner_radius_y = 0;
  double initial_angle = 0, final_angle = 2 * M_PI;

  const double full_angle = (final_angle == initial_angle) ? 2 * M_PI : fabs(final_angle - initial_angle);
  if (inner_radius_x > 0 && inner_radius_y > 0) {
    uint64_t num_points1 = 1 + arc_num_points(full_angle, radius_x > radius_y ? radius_x : radius_y, tolerance);
    if (num_points1 < GDSTK_MIN_POINTS)
      num_points1 = GDSTK_MIN_POINTS;
    uint64_t num_points2 = 1 + arc_num_points(full_angle, inner_radius_x > inner_radius_y ? inner_radius_x : inner_radius_y, tolerance);
    if (num_points2 < GDSTK_MIN_POINTS)
      num_points2 = GDSTK_MIN_POINTS;

    result->points.resize(num_points1 + num_points2);
    Vec2d* v = result->points.data();
    if (full_angle == 2 * M_PI) {
      // Ring
      for (uint64_t i = 0; i < num_points1; i++) {
        const double angle = i * 2 * M_PI / (num_points1 - 1);
        *v++ = center + Vec2{radius_x * cos(angle), radius_y * sin(angle)};
      }
      for (uint64_t i = num_points2; i > 0; i--) {
        const double angle = (i - 1) * 2 * M_PI / (num_points2 - 1);
        *v++ = center + Vec2{inner_radius_x * cos(angle), inner_radius_y * sin(angle)};
      }
    } else {
      // Ring slice
      double initial_ell_angle = elliptical_angle_transform(initial_angle, radius_x, radius_y);
      double final_ell_angle = elliptical_angle_transform(final_angle, radius_x, radius_y);
      for (uint64_t i = 0; i < num_points1; i++) {
        const double angle = LERP(initial_ell_angle, final_ell_angle, (double)i / (double)(num_points1 - 1));
        *v++ = center + Vec2{radius_x * cos(angle), radius_y * sin(angle)};
      }
      initial_ell_angle = elliptical_angle_transform(initial_angle, inner_radius_x, inner_radius_y);
      final_ell_angle = elliptical_angle_transform(final_angle, inner_radius_x, inner_radius_y);
      for (uint64_t i = num_points2; i > 0; i--) {
        const double angle = LERP(initial_ell_angle, final_ell_angle, (double)(i - 1) / (double)(num_points2 - 1));
        *v++ = center + Vec2{inner_radius_x * cos(angle), inner_radius_y * sin(angle)};
      }
    }
  } else {
    uint64_t num_points = 1 + arc_num_points(full_angle, radius_x > radius_y ? radius_x : radius_y, tolerance);
    if (num_points < GDSTK_MIN_POINTS)
      num_points = GDSTK_MIN_POINTS;
    if (full_angle == 2 * M_PI) {
      // Full ellipse
      result->points.resize(num_points);
      Vec2d* v = result->points.data();
      for (uint64_t i = 0; i < num_points; i++) {
        const double angle = i * 2 * M_PI / num_points;
        *v++ = center + Vec2{radius_x * cos(angle), radius_y * sin(angle)};
      }
    } else {
      // Slice
      const double initial_ell_angle = elliptical_angle_transform(initial_angle, radius_x, radius_y);
      const double final_ell_angle = elliptical_angle_transform(final_angle, radius_x, radius_y);
      result->points.resize(num_points + 1);
      Vec2d* v = result->points.data();
      *v++ = center;
      for (uint64_t i = 0; i < num_points; i++) {
        const double angle = LERP(initial_ell_angle, final_ell_angle, (double)i / (num_points - 1.0));
        *v++ = center + Vec2{radius_x * cos(angle), radius_y * sin(angle)};
      }
    }
  }

  return result;
}

Vec2dArray Circle::bounding_box() {
  Vec2dArray res(2);
  Vec2d& min = res[0];
  Vec2d& max = res[1];

  min.x = center.x - radius;
  min.y = center.y - radius;
  max.x = center.x + radius;
  max.y = center.y + radius;

  if (repetition.type != RepetitionType::None) {
    // 获取重复下的极值
    Vec2dArray offsets = {};
    repetition.get_extrema(offsets);

    Vec2d* off = offsets.data();
    Vec2d min0 = min;
    Vec2d max0 = max;

    // 将极值应用到每个包围盒上
    for (uint64_t i = offsets.size(); i > 0; i--, off++) {
      if (min0.x + off->x < min.x)
        min.x = min0.x + off->x;
      if (max0.x + off->x > max.x)
        max.x = max0.x + off->x;
      if (min0.y + off->y < min.y)
        min.y = min0.y + off->y;
      if (max0.y + off->y > max.y)
        max.y = max0.y + off->y;
    }
  }

  return res;
}

void Circle::rotate(double angle, Vec2d _center) {
  double ca = cos(angle);
  double sa = sin(angle);

  Vec2d vt;

  vt = center - _center;
  center.x = vt.x * ca - vt.y * sa + _center.x;
  center.y = vt.x * sa + vt.y * ca + _center.y;
}

CircleParts Circle::get_parts() {
  auto poly = to_polygon();
  auto v_points = connect_end(poly->points);
  auto v_edges = get_edges(v_points);
  auto v_edges_mid = get_edges_mid(v_edges);

  auto box = bounding_box();
  auto box_points = box_to_points(box);
  auto box_edges = get_edges(box_points);
  auto box_edges_mid = get_edges_mid(box_edges);

  auto res = std::make_tuple(center, v_points, v_edges, v_edges_mid, box_points, box_edges, box_edges_mid);
  return res;
}

Vec2dArray Circle::query_parts(GeoPartType type, uint32_t id) {
  auto parts = get_parts();
  if (type == GeoPartType::Center)
    return Vec2dArray{std::get<0>(parts)};
  if (type == GeoPartType::Point)
    return Vec2dArray{std::get<1>(parts)[id]};
  if (type == GeoPartType::Edge)
    return std::get<2>(parts)[id];
  if (type == GeoPartType::EdgeMid)
    return Vec2dArray{std::get<3>(parts)[id]};
  if (type == GeoPartType::BoxPoint)
    return Vec2dArray{std::get<4>(parts)[id]};
  if (type == GeoPartType::BoxEdge)
    return std::get<5>(parts)[id];
  if (type == GeoPartType::BoxEdgeMid)
    return Vec2dArray{std::get<6>(parts)[id]};
  return Vec2dArray{};
}

Vec2dArray Circle::render_aabb() {
  return bounding_box();
}

Vec2dArray Circle::render_poly() {
  auto p = to_polygon();
  return p->points;
}

Vec2dArray Circle::render_absorp() {
  auto p = to_polygon();
  return p->points;
}

}  // namespace qlib